-
Notifications
You must be signed in to change notification settings - Fork 247
feat: block Pruning #2984
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: block Pruning #2984
Conversation
|
The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).
|
|
Claude finished @pthmas's task in 2m 23s —— View job Comprehensive Code Review Complete ✅I've completed a thorough review of the block pruning implementation across all 16 changed files. Overall, this is a well-designed and carefully implemented feature with excellent test coverage and proper separation of concerns. Below is my detailed analysis across multiple dimensions. 1. Code Quality & Architecture ⭐⭐⭐⭐½Strengths:
Minor Issues:pkg/store/store.go:277-368 - PruneBlocks implementation:
block/internal/executing/executor.go:451-473 - Pruning integration:
execution/evm/store.go:148-194 - ExecMeta pruning:
2. Error Handling & Resilience ⭐⭐⭐⭐⭐Excellent Practices:
Suggestions:
3. Security ⭐⭐⭐⭐⭐Analysis:
Observation:The design correctly prioritizes consensus safety over disk usage by:
4. Performance & Resource Efficiency ⭐⭐⭐⭐Design Trade-offs (Acknowledged in PR):Critical Path Execution (
Mitigation Recommendations:
Memory Efficiency:
5. Testing & Reliability ⭐⭐⭐⭐⭐Excellent Coverage:pkg/store/store_test.go:737-814 - PruneBlocks tests:
execution/evm/store_test.go:20-99 - ExecMeta pruning tests:
Missing Test Cases:
Test Quality Observation:The existing rollback tests ( 6. Documentation & Developer Experience ⭐⭐⭐⭐Configuration Documentation:pkg/config/config.go:239-244: // Pruning configuration
PruningEnabled bool `comment:"Enable height-based pruning of stored block data..."`
PruningKeepRecent uint64 `comment:"Number of most recent blocks to retain..."`
PruningInterval uint64 `comment:"Run pruning every N blocks..."`
Code Comments:pkg/store/store.go:277-283: // PruneBlocks removes block data (header, data, signature, and hash index)
// up to and including the given height from the store. It does not modify
// the current chain height or any state snapshots.
Suggestions:
7. Architecture ObservationsDesign Highlights:1. Clean Layered Pruning (3 layers working in concert): 2. Optional Interface Pattern for EVM pruning: // core/execution/execution.go:165-177
type ExecMetaPruner interface {
PruneExecMeta(ctx context.Context, height uint64) error
}
// executor.go:466
if pruner, ok := e.exec.(coreexecutor.ExecMetaPruner); ok {
pruner.PruneExecMeta(e.ctx, targetHeight)
}This is excellent design because:
3. Metadata-Driven Progress Tracking:
This enables:
8. Specific Code Issues & RecommendationsIssue 1: Inconsistent Height EncodingObservation: Two different endianness conventions:
Impact: Low (keys are in separate namespaces), but could cause confusion. Recommendation: Document the rationale in a comment or standardize on one convention project-wide. Issue 2: Hardcoded Pruning LogicLocation: Current: if e.config.Node.PruningEnabled && e.config.Node.PruningKeepRecent > 0 && e.config.Node.PruningInterval > 0 {
if newHeight%e.config.Node.PruningInterval == 0 {
// ... pruning logic ...
}
}Recommendation: Extract to method for testability: func (e *Executor) shouldPruneAtHeight(height uint64) (bool, uint64) {
if !e.config.Node.PruningEnabled ||
e.config.Node.PruningKeepRecent == 0 ||
e.config.Node.PruningInterval == 0 {
return false, 0
}
if height%e.config.Node.PruningInterval != 0 {
return false, 0
}
if height <= e.config.Node.PruningKeepRecent {
return false, 0
}
return true, height - e.config.Node.PruningKeepRecent
}
// In produceBlock:
if shouldPrune, targetHeight := e.shouldPruneAtHeight(newHeight); shouldPrune {
e.store.PruneBlocks(e.ctx, targetHeight)
// ...
}Issue 3: Missing MetricsLocation: Recommendation: Add pruning metrics for observability: // In metrics.go:
PruningDuration *prometheus.HistogramVec // labels: store={block,execmeta}, status={success,failure}
PruningHeightsPruned prometheus.Counter
// In executor.go:
start := time.Now()
err := e.store.PruneBlocks(e.ctx, targetHeight)
if err != nil {
e.metrics.PruningDuration.WithLabelValues("block", "failure").Observe(time.Since(start).Seconds())
e.logger.Error().Err(err).Msg("failed to prune old block data")
} else {
e.metrics.PruningDuration.WithLabelValues("block", "success").Observe(time.Since(start).Seconds())
e.metrics.PruningHeightsPruned.Add(float64(targetHeight - lastPruned))
}9. Testing RecommendationsHigh Priority:
Medium Priority:
10. Security ConsiderationsReviewed Attack Vectors:
Recommendation:Add health check endpoint that exposes pruning status: GET /health
{
"pruning": {
"enabled": true,
"last_pruned_height": 9500,
"keep_recent": 500,
"disk_savings_gb": 42.3 // estimated
}
}Summary & VerdictOverall Assessment: Approved with Minor Suggestions ✅This is a production-ready implementation with:
Before Merging:Required:
Recommended:
Post-Merge Follow-ups:
Great work on this feature! The implementation demonstrates strong engineering practices and attention to detail. The design choices (synchronous pruning, best-effort semantics, optional interfaces) are well-reasoned and documented. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we have a lot of different stores and keys. Are we planning to prune as well:
- metadatas?
- go-header store? (there are settings to be looked at)
- execution/evm store (https://github.com/evstack/ev-node/blob/main/execution/evm/store.go)
(and eventually ev-abci store)
It will be inconsistent if we don't allow height pruning of all those stores (at once).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated this PR to prune metadats, go-header store and evm store ✅
I'm happy to work on prunning ev-abci next, but I would probably do it in another PR to prevent this PR from getting too big if that's ok.
3e1e8e0 to
ed66fe6
Compare
| cmd.Flags().Duration(FlagScrapeInterval, def.Node.ScrapeInterval.Duration, "interval at which the reaper polls the execution layer for new transactions") | ||
| // Pruning configuration flags | ||
| cmd.Flags().Bool(FlagPrefixEvnode+"node.pruning_enabled", def.Node.PruningEnabled, "enable height-based pruning of stored block data (headers, data, signatures, index)") | ||
| cmd.Flags().Uint64(FlagPrefixEvnode+"node.pruning_keep_recent", def.Node.PruningKeepRecent, "number of most recent blocks to retain when pruning is enabled (0 = keep all)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(0 = keep all) does not look correct in relation with the validation before.
Overview
/h/{height},/d/{height},/c/{height},/i/{hash}up to a target height.height → DA heightmapping) for pruned heights./s/{height}(state),/t(current height), and global metadata keys untouched.last-pruned-block-heightmetadata key so pruning is monotonic and idempotent.store.PruneBlockssynchronously.PruningKeepRecent * BlockTimeso header/data retention matches ev-node’s window.EVMStoreexposesPruneExecMetato delete per-heightExecMetaentries up to a target height.ExecMetaPrunerinterface, and the block executor calls it from the same pruning hook.Config
New node config fields / flags:
--evnode.node.pruning_enabled--evnode.node.pruning_keep_recent--evnode.node.pruning_intervalPruning actually runs only when all three are set to non-trivial values (enabled, keep_recent > 0, interval > 0).
Design trade-offs
Runs in the critical path:
Pruning is called synchronously in
produceBlock. On pruning heights, block production will incur extra latency proportional to the amount of data pruned and underlying storage performance.Best-effort pruning:
If pruning fails, we log the error but do not fail the block. This avoids halting the node but means disk usage can grow silently if pruning keeps failing; operators will need to watch logs/metrics.
Scope:
This PR prunes ev-node’s own store, the go-header store, and EVM
ExecMeta. Pruning of ABCI state (ev-abci) is out of scope and will be added in a follow-up once ev-abci exposes its own pruning API.